﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace PhantomMvvmToolkit
{
    /// <summary>
    /// This IDisposable object will force any PropertyChanged or CanExecuteChanged from firing until it is disposed.  The order of 
    /// the PropertyChanged calls will be first in, first out.  Any repeated events move that event to the back of the queue.  Only 
    /// the last EventArgs CanExecuteChanged event will be processed.
    /// </summary>
    public class DelayEvents : IDisposable
    {
        /// <summary>
        /// Holds the currently active DelayEvents objects.
        /// </summary>
        internal static readonly List<DelayEvents> InDelay = new List<DelayEvents>();

        /// <summary>
        /// Initializes a new instance of the <see cref="DelayEvents"/> class.
        /// </summary>
        /// <param name="notifyChanged">The notify changed.</param>
        /// <param name="onlyRootObject">If set to <c>true</c> [only root object].</param>
        internal DelayEvents(object notifyChanged, bool onlyRootObject)
        {
            InDelay.Add(this);

            if (notifyChanged is IPhantomCommand)
            {
                Command = (IPhantomCommand)notifyChanged;
            }

            if (notifyChanged is IPhantomNotifyPropertyChanged)
            {
                InternalCurrentDelayedEvents = new List<string>();
                NotifyProperty = (IPhantomNotifyPropertyChanged)notifyChanged;
            }

            Children = new List<DelayEvents>();

            if (!onlyRootObject)
            {
                var realType = notifyChanged.GetType();
                if (!realType.IsByRef)
                {
                    foreach (var prop in realType.GetProperties())
                    {
                        var value = prop.GetValue(notifyChanged, null);
                        if (value != null && (value is IPhantomCommand || value is IPhantomNotifyPropertyChanged))
                            this.Children.Add(new DelayEvents(value, false));
                    }
                }
            }
        }

        /// <summary>
        /// Gets the current delayed notify property changed events.
        /// </summary>
        /// <value>The current delayed notify property changed events.</value>
        public ReadOnlyCollection<string> CurrentDelayedNotifyPropertyChangedEvents
        {
            get
            {
                return new ReadOnlyCollection<string>(InternalCurrentDelayedEvents);
            }
        }

        /// <summary>
        /// Gets the current delayed command event.
        /// </summary>
        /// <value>The current delayed ICommand CanExecuteChanged EventArgs.</value>
        public EventArgs CurrentDelayedCommandEvent { get; internal set; }

        /// <summary>
        /// Gets or sets the internal current delayed events.
        /// </summary>
        /// <value>The internal current delayed events.</value>
        internal List<string> InternalCurrentDelayedEvents { get; set; }

        /// <summary>
        /// Gets the command object to be delayed.
        /// </summary>
        /// <value>The command.</value>
        internal IPhantomCommand Command { get; private set; }

        /// <summary>
        /// Gets the notify property object to be delayed.
        /// </summary>
        /// <value>The notify property.</value>
        internal IPhantomNotifyPropertyChanged NotifyProperty { get; private set; }

        /// <summary>
        /// Gets or sets the children.
        /// </summary>
        /// <value>The children of the DelayEvents object that need to be disposed when this is disposed.</value>
        private List<DelayEvents> Children { get; set; }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.  This will
        /// also cause all the delayed events to fire in the order they were delayed.
        /// </summary>
        public void Dispose()
        {
            InDelay.Remove(this);

            if (NotifyProperty != null)
            {
                foreach (var property in InternalCurrentDelayedEvents)
                {
                    NotifyProperty.OnPropertyChanged(property);
                }
            }

            if (Command != null)
            {
                if (CurrentDelayedCommandEvent == null)
                    CurrentDelayedCommandEvent = EventArgs.Empty;

                Command.InvokeCanExecuteChanged(CurrentDelayedCommandEvent);
            }

            foreach (var child in this.Children)
            {
                child.Dispose();
            }
        }

        /// <summary>
        /// Adds the event to the queue.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        internal void AddEvent(string propertyName)
        {
            if (InternalCurrentDelayedEvents != null)
            {
                if (!InternalCurrentDelayedEvents.Contains(propertyName))
                    InternalCurrentDelayedEvents.Remove(propertyName);

                InternalCurrentDelayedEvents.Add(propertyName);
            }
        }
    }
}
